#include "Node.h"
#include "Imagem.h"
#include "Pagina.h"
#include "Util.h"
#include <vector>
#include <iostream>
#include <map>
#include <opencv/cv.h>

using namespace std;

Node::Node(Imagem imagem) {
    this->setType(LEAF);
    this->setImage(imagem);
}

Node::Node(Node* left, NodeType type, Node* right) {
    this->setType(type);
    this->setLeft(left);
    this->setRight(right);
}

Node::Node(vector<Imagem> imagens, double width, double height) {
    this->setBounds(width, height);
    if(imagens.size() == 1) {
        this->setImage(imagens.at(0));
        this->setLeft(NULL);
        this->setRight(NULL);
        this->setType(LEAF);
    } else {
        int d = imagens.size() * GOLDEN_RATIO;
        int total = 0;
        int sum = 0;
        int limit = 0;
//        int d;
/*        for(int i = 0; i < imagens.size(); i++) {
            total += imagens.at(i).getOrdem();
            cout << imagens.at(i).getOrdem() << " ";
        }
        limit = total * GOLDEN_RATIO;
        cout << total << " " << limit << endl;
        for(d = 0; d < imagens.size() && sum < limit; d++)
            sum += imagens.at(d).getOrdem();*/
        vector<Imagem> a, b;
        for(int i = 0; i < imagens.size(); i++)
            if(i < d)
                a.push_back(imagens.at(i));
            else
                b.push_back(imagens.at(i));
        if(width > height) {
            this->setType(BISS_V);
            this->setLeft(new Node(a, width - width * GOLDEN_RATIO, height));
            this->setRight(new Node(b, width * GOLDEN_RATIO, height));
        } else {
            this->setType(BISS_H);
            this->setLeft(new Node(a, width, height - height * GOLDEN_RATIO));
            this->setRight(new Node(b, width, height * GOLDEN_RATIO));
        }
    }
}

void Node::setImage(Imagem image) {
    this->image = image;
}

Imagem Node::getImage() {
    return this->image;
}

void Node::setLeft(Node* left) {
    this->left = left;
}

void Node::setRight(Node* right) {
    this->right = right;
}

NodeType Node::getType() {
    return this->type;
}

bool Node::isLeaf() {
    return this->type == LEAF;
}

Node* Node::getRight() {
    return this->right;
}

Node* Node::getLeft() {
    return this->left;
}

void Node::setType(NodeType type) {
    this->type = type;
}

double Node::getHeight() {
    return this->height;
}

double Node::getWidth() {
    return this->width;
}

void Node::setBounds(double width, double height) {
    this->width = width;
    this->height = height;
}

int Node::getNumImages() {
    if(this->type == LEAF) {
        return 1;
    } else {
        return this->getLeft()->getNumImages() + this->getRight()->getNumImages();
    }
}

void Node::normalize(double alpha, double beta, Util u, SolveType type) {
    int i = this->getNumImages();
//    this->enumImages(&i);
    
    vector<map<int, double> > A;
    vector<double> B;
    
    map<int, double> r;
    double b;
    
    this->treatNodes(&A,&B,alpha,beta,type);
    this->rootEq(&r,&b,alpha,beta,type);
    
    A.push_back(r);
    B.push_back(b);
    
    /*for(int a = 0; a < A.size(); a++) {
        for(int b = 0; b < i; b++)
            cout << A.at(a)[b] << " ";
        cout << B.at(a) << endl;
    }*/

    CvMat* zA  = cvCreateMat(i,i,CV_32FC1);
    CvMat* zX  = cvCreateMat(i,1,CV_32FC1);
    CvMat* zB  = cvCreateMat(i,1,CV_32FC1);

    for(int a = 0; a < i; a++) {
        for(int b = 0; b < i; b++)
            cvmSet(zA,a,b,A.at(a)[b]);
        cvmSet(zB,a,0,B[a]);
        cvmSet(zX,a,0,0);
    }

    cout << "matrixes before solving\n";
    for(int a = 0; a < i; a++) {
        for(int b = 0; b < i; b++)
            cout << cvmGet(zA, a, b) << " ";
        cout << " * " << cvmGet(zX, a, 0) << " " << cvmGet(zB, a, 0)<< endl;
    }
    cout << "end mat\n";
    
    cvSolve(zA, zB, zX);    // solve (Ax=b) for x
    
    cout << "matrixes after solving\n";
    for(int a = 0; a < i; a++) {
        for(int b = 0; b < i; b++)
            cout << cvmGet(zA, a, b) << " ";
        cout << " * " << cvmGet(zX, a, 0) << " " << cvmGet(zB, a, 0)<< endl;
    }
    cout << "end mat\n";

    vector<double> vals;

    for(int a = 0; a < i; a++) {
        vals.push_back(cvmGet(zX,a,0));
    }
    
//    this->applyMetrics(widths, alpha, beta, u);
    if(type == WIDTH)
        this->applyWidths(vals);
    else if(type == HEIGHT)
        this->applyHeights(vals);
    
    free(zA);
    free(zB);
    free(zX);
}

void Node::applyWidths(vector<double> widths) {
    if(this->type == LEAF) {
        this->setBounds(widths[this->getImage().getId()], widths[this->getImage().getId()]/this->getImage().getAspectRatio());
        cout << this->getImage().getName() << ":" << this->getWidth() << " " << this->getHeight() << " " << this->getImage().getAspectRatio() << endl;
    } else {
        this->getLeft()->applyWidths(widths);
        this->getRight()->applyWidths(widths);
    }
}

void Node::applyHeights(vector<double> heights) {
    if(this->type == LEAF) {
        this->setBounds(heights[this->getImage().getId()]*this->getImage().getAspectRatio(), heights[this->getImage().getId()]);
        cout << this->getImage().getName() << ":" << this->getWidth() << " " << this->getHeight() << " " << this->getImage().getAspectRatio() << endl;
    } else {
        this->getLeft()->applyHeights(heights);
        this->getRight()->applyHeights(heights);
    }
}

/*void Node::applyMetrics(vector<double> widths, double alpha, double beta, Util u) {
    if(this->type == LEAF) {
        this->setBounds(widths[this->getNum()], widths[this->getNum()]/this->getImage().getAspectRatio());
        cout << this->getImage().getName() << ":" << this->getWidth() << " " << this->getHeight() << " " << this->getImage().getAspectRatio() << endl;

    } else {
        this->getLeft()->applyMetrics(widths, alpha, beta, u);
        this->getRight()->applyMetrics(widths, alpha, beta, u);
    }
}*/

void Node::treatNodes(vector<map<int, double> > *A, vector<double> *B, double alpha, double beta, SolveType solveType) {
    if(this->type != LEAF) {
        map<int, double> m;
        double b;
        this->treatSubTree(&m, &b, alpha, beta, solveType);
        B->push_back(b);
        A->push_back(m);
        this->getLeft()->treatNodes(A,B,alpha,beta,solveType);
        this->getRight()->treatNodes(A,B,alpha,beta,solveType);
    }
}

void Node::treatSubTree(map<int, double> *A, double* B, double alpha, double beta, SolveType solveType) {
    if(this->type != LEAF) {
        this->getLeft()->treatNode(A,B,alpha,beta,this->getType(),false,solveType);
        this->getRight()->treatNode(A,B,alpha,beta,this->getType(),true,solveType);
    }
}

void Node::rootEq(map<int, double> *A, double *B, double alpha, double beta, SolveType solveType) {
    if(solveType == WIDTH) {
        this->treatNode(A,B,alpha,beta,BISS_H,false,solveType);
        *(B) = this->getWidth() + *(B);
    } else if(solveType == HEIGHT) {
        this->treatNode(A,B,alpha,beta,BISS_V,false,solveType);
        *(B) = this->getHeight() + *(B);  
    }
}

void Node::treatNode(map<int, double> *A, double* B, double alpha, double beta, NodeType type, bool opposite, SolveType solveType) {
    if(this->type == LEAF)
        this->treatLeaf(A,B,alpha,beta,type,opposite,solveType);
    else
        this->treatBiss(A,B,alpha,beta,type,opposite,solveType);
}

void Node::treatBiss(map<int, double> *A, double* B, double alpha, double beta, NodeType type, bool opposite, SolveType solveType) {
    if(this->type == type) {
        this->getLeft()->treatNode(A,B,alpha,beta,type,opposite,solveType);
    } else {
        if(opposite) {
            *(B) = *(B) + alpha;
        } else {
            *(B) = *(B) - alpha;
        }
        this->getLeft()->treatNode(A,B,alpha,beta,type,opposite,solveType);
        this->getRight()->treatNode(A,B,alpha,beta,type,opposite,solveType);
    }
}

/*void Node::treatLeaf(map<int, double> *A, double* B, double alpha, double beta, NodeType type, bool opposite, SolveType solveType) {
    switch(type) {
        case BISS_H:
            if(opposite) {
                if(solveType == WIDTH) {
                    A->insert(pair<int, double>(this->getNum(), -1));
                    *(B) = *(B) + 2*beta;
                } else if(solveType == HEIGHT) {
                    A->insert(pair<int, double>(this->getNum(), -1));
                    *(B) = *(B) + 2*beta;
                }
            } else {
                if(solveType == WIDTH) {
                    A->insert(pair<int, double>(this->getNum(), 1));
                    *(B) = *(B) - 2*beta;
                } else if(solveType == HEIGHT) {
                    A->insert(pair<int, double>(this->getNum(), 1));
                    *(B) = *(B) - 2*beta;
                }
            }
            break;
        case BISS_V:
            if(opposite) {
                A->insert(pair<int, double>(this->getNum(), -this->getImage().getArea()));
                *(B) = *(B) + 2*beta;
            } else {
                A->insert(pair<int, double>(this->getNum(), this->getImage().getArea()));
                *(B) = *(B) - 2*beta;
            }
            break;
    }
}*/

void Node::treatLeaf(map<int, double> *A, double* B, double alpha, double beta, NodeType type, bool opposite, SolveType solveType) {
    if((solveType == WIDTH && type == BISS_H) || (solveType == HEIGHT && type == BISS_V)) {
        A->insert(pair<int, double>(this->getImage().getId(), opposite ? -1 : 1));
        *(B) = *(B) + (opposite ? 2*beta : -2*beta);
    } else {
        A->insert(pair<int, double>(this->getImage().getId(), opposite ? -this->getImage().getArea() : this->getImage().getArea()));
        *(B) = *(B) + (opposite ? 2*beta : -2*beta);
    }
/*    switch(type) {
        case BISS_H:
            if(opposite) {
                if(solveType == WIDTH) {
                    A->insert(pair<int, double>(this->getNum(), -1));
                    *(B) = *(B) + 2*beta;
                } else if(solveType == HEIGHT) {
                    A->insert(pair<int, double>(this->getNum(), -1));
                    *(B) = *(B) + 2*beta;
                }
            } else {
                if(solveType == WIDTH) {
                    A->insert(pair<int, double>(this->getNum(), 1));
                    *(B) = *(B) - 2*beta;
                } else if(solveType == HEIGHT) {
                    A->insert(pair<int, double>(this->getNum(), 1));
                    *(B) = *(B) - 2*beta;
                }
            }
            break;
        case BISS_V:
            if(opposite) {
                A->insert(pair<int, double>(this->getNum(), -this->getImage().getArea()));
                *(B) = *(B) + 2*beta;
            } else {
                A->insert(pair<int, double>(this->getNum(), this->getImage().getArea()));
                *(B) = *(B) - 2*beta;
            }
            break;
    }*/
}

void Node::enumImages(int *i) {
    if(this->type == LEAF) {
        this->setNum((*i)++);
    } else {
        this->getLeft()->enumImages(i);
        this->getRight()->enumImages(i);
    }
}

void Node::setNum(int n) {
    this->num = n;
}

int Node::getNum() {
    return this->num;
}
